#include "LightingCommon.fxh"

cbuffer cbPerFrame
{
	float4x4 gProj;
	float4x4 gProjInv;
};

cbuffer cbPerRun
{
	float2 gBufferRes;
};

Texture2D gDepthTexture;
Texture2D gRandom4x4;

struct VS_IN
{
	float3 posL     : POSITION;
};

struct VS_OUT
{
	float4 posD         : SV_POSITION;
	noperspective float2 texCoord         : TEXCOORD0;
	float3 viewRayV     : TEXCOORD1;
};

VS_OUT VS(VS_IN vIn)
{
	VS_OUT vOut;

	float4 posH = float4(vIn.posL, 1.0f);
	vOut.posD = posH;

	// SV_POSITION interpolator will be in window coords in PS, so another copy in [0;1] is useful
	vOut.texCoord = (posH / posH.w).xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);

	// multiplication gives view space vertex position, which is the same thing as view ray in view space
	vOut.viewRayV = mul(posH, gProjInv).xyz;
	
	return vOut;
}

float PS2(VS_OUT pIn) : SV_Target
{
	const float PI = 3.14159265;

	float depth = gDepthTexture.Sample(gPointSam, pIn.texCoord);
	float occlusion = 0;

	float sample;
	float pPos;
	float dist = (pIn.texCoord.x * pIn.texCoord.y * 300 * 512) % 4;
	float depthInv = 1.0f / depth;
	dist *= 200 * depthInv; // magic!

	//const float2[] = {  }

	for(int j=1;j<3;j++)
	{
		for(float i=0; i<PI; i+=PI/4)
		{
			float2 offset = float2(cos(i), sin(i)) * j * dist * float2(0.002f, 0.002f); // magic!
			float sampleDepth1 = gDepthTexture.Sample(gPointSam, pIn.texCoord + offset);
			float sampleDepth2 = gDepthTexture.Sample(gPointSam, pIn.texCoord - offset);

			//if(sampleDepth1 > depth + 0.01) occlusion += -0.2f * (sampleDepth1 - depth) + 5;

			float value = (depth/sampleDepth1) - (sampleDepth2 * depthInv /* / depth */);
			if((value>0.005)&&(value<0.05))
				occlusion += (value*5);
		}
	}

	return 1.0f - occlusion;
}

float PS2_O(VS_OUT pIn) : SV_Target
{
	const float PI = 3.14159265f;

	float depth = gDepthTexture.Sample(gPointSam, pIn.texCoord);
	float occlusion = 0.0f;

	float sample;
	float pPos;
	float dist = (pIn.texCoord.x * pIn.texCoord.y * 300 * 512) % 4;
	float depthInv = 1.0f / depth;
	dist *= 200 * depthInv; // magic!

	const float4 trigValues[] = { float4(cos(0.0f), sin(0.0f), cos(PI/4), sin(PI/4)),
		float4(cos(PI/2), sin(PI/2), cos(3*PI/4), sin(3*PI/4)) };

	for(int j=1;j<3;j++)
	{
		for(int i=0; i<2; i++)
		{
			float4 trigValue = trigValues[i];
			float4 offset = trigValue * j * dist * float4(0.002f, 0.002f, 0.002f, 0.002f); // magic!
			float sampleDepth1 = gDepthTexture.Sample(gPointSam, pIn.texCoord + offset.xy);
			float sampleDepth2 = gDepthTexture.Sample(gPointSam, pIn.texCoord - offset.xy);
			float sampleDepth3 = gDepthTexture.Sample(gPointSam, pIn.texCoord + offset.zw);
			float sampleDepth4 = gDepthTexture.Sample(gPointSam, pIn.texCoord - offset.zw);

			//if(sampleDepth1 > depth + 0.01) occlusion += -0.2f * (sampleDepth1 - depth) + 5;

			float2 value = (depth/float2(sampleDepth1, sampleDepth3)) - (float2(sampleDepth2, sampleDepth4) * depthInv /* / depth */);
			if((value.x>0.005)&&(value.x<0.05))
				occlusion += (value.x*5);
			if((value.y>0.005)&&(value.y<0.05))
				occlusion += (value.y*5);
		}
	}

	return 1.0f - occlusion;
}

float PS(VS_OUT pIn) : SV_Target
{
	const float NUM_SAMPLES = 16;
	const float SPHERE_RADIUS = 5; // camera space; MAGIC!
	//const float2 samples[] = { float2(1, 0), float2(0.707f, 0.707f), float2(0, 1), float2(-0.707f, 0.707f), float2(-1, 0), float2(-0.707f, -0.707f), float2(0, -1), float2(0.707f, -0.707f) };

	//const float3 samples[] = { float3(0.24710192, 0.6445882, 0.033550154), float3(0.00991752, -0.21947019, 0.7196721),
	//						   float3(0.25109035, -0.1787317, -0.011580509), float3(-0.08781511, 0.44514698, 0.56647956), 
	//						   float3(-0.011737816, -0.0643377, 0.16030222), float3(0.035941467, 0.04990871, -0.46533614),
	//						   float3(-0.058801126, 0.7347013, -0.25399926), float3(-0.24799341, -0.022052078, -0.13399573) };

	// sphere full someone else's
	//const float3 samples[] = { float3(0.53812504, 0.18565957, -0.43192), float3(0.13790712, 0.24864247, 0.44301823),
							   //float3(0.33715037, 0.56794053, -0.005789503), float3(-0.6999805, -0.04511441, -0.0019965635),
							   //float3(0.06896307, -0.15983082, -0.85477847), float3(0.056099437, 0.006954967, -0.1843352),
							   //float3(-0.014653638, 0.14027752, 0.0762037), float3(0.010019933, -0.1924225, -0.034443386),
							   //float3(-0.35775623, -0.5301969, -0.43581226), float3(-0.3169221, 0.106360726, 0.015860917),
							   //float3(0.010350345, -0.58698344, 0.0046293875), float3(-0.08972908, -0.49408212, 0.3287904),
							   //float3(0.7119986, -0.0154690035, -0.09183723), float3(-0.053382345, 0.059675813, -0.5411899),
							   //float3(0.035267662, -0.063188605, 0.54602677), float3(-0.47761092, 0.2847911, -0.0271716) };

	// only lengths randomized
	const float3 samples[] = { float3(0.0760228, 0, -0.0760228), float3(0.495917, 0.205415, -0.536776), 
								float3(0.0784112, 0.0784112, -0.11089), float3(0.199544, 0.481741, -0.521433), 
								float3(-3.90579e-009, 0.0893541, -0.0893541), float3(-0.141706, 0.342108, -0.370295), 
								float3(-0.195993, 0.195993, -0.277175), float3(-0.281833, 0.116739, -0.305054), 
								float3(-0.389496, -3.40508e-008, 0.389496), float3(-0.132538, -0.0548991, 0.143458), 
								float3(-0.300918, -0.300918, 0.425562), float3(-0.136765, -0.330181, 0.357386), 
								float3(2.74807e-009, -0.230448, 0.230448), float3(0.20219, -0.488131, 0.528349), 
								float3(0.126892, -0.126892, 0.179452), float3(0.354276, -0.146746, 0.383465) };

	// sphere full
	//const float3 samples[] = { float3(-0.684883, 0.0873156, -0.421156), float3(0.439415, -0.104037, -0.773847), 
								//float3(0.53244, 0.40671, -0.537474), float3(0.0109545, 0.000704357, -0.0102001), 
								//float3(-0.121803, -0.0404069, -0.105136), float3(0.0036682, -0.000407781, -0.0028602), 
								//float3(-0.553221, -0.13757, 0.0356689), float3(0.18485, 0.194662, -0.606272), 
								//float3(-0.0636844, -0.191369, -0.573239), float3(0.206134, 0.220167, 0.0144661), 
								//float3(0.549906, 0.331542, 0.666811), float3(0.0255962, -0.232628, -0.0246632), 
								//float3(0.563853, -0.452028, 0.435306), float3(0.273286, 0.274881, 0.0613356), 
								//float3(-0.0120715, -0.0104673, 0.0175632), float3(-0.0151284, -0.0496475, 0.0215963) };

	// sphere, length > 0.5
	//const float3 samples[] = { float3(-0.765868, 0.0976402, -0.470955), float3(0.464927, -0.110077, -0.818776), 
							//float3(0.576159, 0.440105, -0.581606), float3(0.371001, 0.0238549, -0.345452), 
							//float3(-0.428002, -0.141986, -0.369437), float3(0.394631, -0.0438698, -0.307705), 
							//float3(-0.760886, -0.189211, 0.0490581), float3(0.23182, 0.244125, -0.760324), 
							//float3(-0.0842414, -0.253142, -0.758278), float3(0.444404, 0.474658, 0.0311875), 
							//float3(0.571969, 0.344844, 0.693564), float3(0.067182, -0.610578, -0.0647334), 
							//float3(0.6161, -0.493913, 0.475642), float3(0.484834, 0.487664, 0.108815), 
							//float3(-0.260243, -0.225659, 0.378635), float3(-0.142122, -0.466408, 0.202884) };

	float3 viewRayV = normalize(pIn.viewRayV);
	
	float depth = gDepthTexture.Sample(gPointSam, pIn.texCoord);
	float3 posV = viewRayV * depth;

	float3 normalV = getNormalFromGBuffer(pIn.texCoord);

	float3 randomVec = gRandom4x4.Sample(gPointSamWrap, pIn.texCoord * gBufferRes / float2(4.0f, 4.0f)).xyz * 2.f - 1.f;

	float numUsedSamples = NUM_SAMPLES;

	float occlusion = 0.0f;
	for(int i=0; i<NUM_SAMPLES; i++)
	{
		float3 sampleOffset = samples[i];
		
		sampleOffset = reflect(sampleOffset, randomVec);

		float hemisphereTest = dot(sampleOffset, normalV);
		sampleOffset = hemisphereTest > 0 ? sampleOffset : -sampleOffset;
		float3 samplePoint = posV + sampleOffset * SPHERE_RADIUS;

		float sampleDepth = length(samplePoint);

		float4 sampleProj = mul(float4(samplePoint, 1.0f), gProj);
		sampleProj.xy /= sampleProj.w;
		float sceneDepth = gDepthTexture.Sample(gBilinearSam, sampleProj.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f)).r;

		float depthDiff = (sampleDepth - sceneDepth);

		float amount = 1.8f * /*saturate(dot(sampleOffset, normalV)) **/ saturate( -(1.0f / SPHERE_RADIUS) * depthDiff + 1.0f) ; // MAGICS! rather depend on ssao kernel radius
		//numUsedSamples = amount > 0.0f ? numUsedSamples : numUsedSamples - 1;
		amount = depthDiff > 0.25f ? amount : 0.0f; // MAGIC!
		
		occlusion += amount;
	}

	float factor = occlusion / NUM_SAMPLES;
	return 1.0f - factor.x;
}

technique11 tech
{
	pass P0
	{
		SetVertexShader( CompileShader( vs_5_0, VS() ) );
		SetGeometryShader( NULL );
		SetPixelShader( CompileShader( ps_5_0, PS() ) );
		
		SetDepthStencilState(DepthOff, 0);
		SetRasterizerState(CullBack);
	}
}


